﻿using System;
using FluentNHibernate;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Inspections;
using FluentNHibernate.Conventions.Instances;

namespace HashFoo.Nhib
{
    /// <summary>
    /// Maps all foreign key column names to use an "Id" suffix.  Also ensure <see cref="IReferenceConvention"/>s and
    /// <see cref="IHasManyConvention"/>s use the foreign key property name for the root of the column name.
    /// </summary>
    /// <remarks>
    /// The <see cref="ForeignKeyConvention"/> in FluentNHibernate will map two different column names to a foreign key property.  This
    /// custom version of the convention ensures that the <see cref="IReferenceConvention"/> and <see cref="IHasManyConvention"/> produce the same
    /// key name for the foreign key column.
    /// </remarks>
    public class ForeignKeyColumnNameConvention : IReferenceConvention, IHasManyConvention, IHasManyToManyConvention, IJoinedSubclassConvention, IJoinConvention
    {
        /// <summary>
        /// Apply changes to the target
        /// </summary>
        public void Apply(IManyToOneInstance instance)
        {
            var keyName = GetKeyName(instance.Property, instance.Class.GetUnderlyingSystemType());
            instance.Column(keyName);
        }

        /// <summary>
        /// Apply changes to the target
        /// </summary>
        public void Apply(IManyToManyCollectionInstance instance)
        {
            var keyName1 = GetKeyName(null, instance.EntityType);
            var keyName2 = GetKeyName(null, instance.ChildType);
            instance.Key.Column(keyName1);
            instance.Relationship.Column(keyName2);
        }

        /// <summary>
        /// Apply changes to the target
        /// </summary>
        public void Apply(IJoinedSubclassInstance instance)
        {
            var keyName = GetKeyName(null, instance.Type.BaseType);
            instance.Key.Column(keyName);
        }

        /// <summary>
        /// Apply changes to the target
        /// </summary>
        public void Apply(IJoinInstance instance)
        {
            var keyName = GetKeyName(null, instance.EntityType);
            instance.Key.Column(keyName);
        }

        /// <summary>
        /// Apply changes to the target
        /// </summary>
        public void Apply(IOneToManyCollectionInstance instance)
        {
            // there is a goofy pass through, where the instance "OtherSide" may not point to the 
            //  child type.  If we get one of those, ignore the otherside and create a "default" from the entity type.
            if (instance.ChildType.Name != instance.OtherSide.EntityType.Name)
            {
                instance.Key.Column(instance.EntityType.Name + "Id");
                return;
            }

            var keyColumnName = instance.OtherSide.Name + "Id";
            instance.Key.Column(keyColumnName);
        }

        /// <summary>
        /// Creates the key name for a property from a type.
        /// </summary>
        /// <param name="property"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        protected virtual string GetKeyName(Member property, Type type)
        {
            if (property != null)
            {
                return property.Name + "Id";
            }

            return type.Name + "Id";
        }
    }
}